using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Core
{
    /**
     * An operator for arithmetic operations. Note that the + operator
     * also does string concatenation for backward compatibility.
     * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
     */
    internal sealed class ArithmeticExpression : Expression
    {
        internal enum OperationType
        {
            Subtraction = 0,
            Multiplication = 1,
            Division = 2,
            Modulus = 3
        }

        private static readonly char[] Operators = new[] {'-', '*', '/', '%'};

        private readonly Expression _left;
        private readonly Expression _right;
        private readonly OperationType _operation;

        internal ArithmeticExpression(Expression left, Expression right, OperationType operation)
        {
            _left = left;
            _right = right;
            _operation = operation;
        }

        protected override ITemplateModel _getAsTemplateModel(Environment env)
        {
            ITemplateModel leftModel = _left.GetAsTemplateModel(env);
            ITemplateModel rightModel = _right.GetAsTemplateModel(env);
            bool leftIsNumber = (leftModel is ITemplateNumberModel);
            bool rightIsNumber = (rightModel is ITemplateNumberModel);
            bool bothNumbers = leftIsNumber && rightIsNumber;
            if (!bothNumbers)
            {
                string msg = "Error " + GetStartLocation();
                if (!leftIsNumber)
                {
                    msg += "\nExpression " + _left + " is not numerical";
                }
                if (!rightIsNumber)
                {
                    msg += "\nExpression " + _right + " is not numerical";
                }
                throw new NonNumericalException(msg, env);
            }
            Number first = EvaluationUtil.GetNumber(leftModel, _left, env);
            Number second = EvaluationUtil.GetNumber(rightModel, _right, env);
            ArithmeticEngine ae =
                env != null
                    ? env.GetArithmeticEngine()
                    : GetTemplate().GetArithmeticEngine();
            switch (_operation)
            {
                case OperationType.Subtraction:
                    return new SimpleNumber(ae.Subtract(first, second));
                case OperationType.Multiplication:
                    return new SimpleNumber(ae.Multiply(first, second));
                case OperationType.Division:
                    return new SimpleNumber(ae.Divide(first, second));
                default: // case OperationType.Modulus:
                    return new SimpleNumber(ae.Modulus(first, second));
            }
        }

        public override string GetCanonicalForm()
        {
            return _left.GetCanonicalForm() + ' ' + Operators[(int) _operation] + ' ' + _right.GetCanonicalForm();
        }

        internal override bool IsLiteral()
        {
            return ConstantValue != null || (_left.IsLiteral() && _right.IsLiteral());
        }

        protected override Expression DeepCloneImpl(string name, Expression subst)
        {
            return new ArithmeticExpression(_left.DeepClone(name, subst), _right.DeepClone(name, subst), _operation);
        }
    }
}